﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Package;
using Microsoft.VisualStudio.TextManager.Interop;
using Irony.Parsing;
using HumanWorkflow.TextualDslParser.IronyParser;

namespace HumanWorkflow.HwdlLanguagePackage
{
    
    public class HwdlLanguageService : LanguageService
    {

        public HwdlLanguageService()
        {
            parser = new Parser(new HwdlGrammar());
            // VS treats tabs like normal chars
            // TODO: Check if there is a possibility to change it through preferences in VS
            // In that case we have to implement some logic here
            parser.Context.TabWidth = 1;
        }

        public override string GetFormatFilterList()
        {
            return "Hwdl (*.hwdl)\n*.hwdl";
        }

        private LanguagePreferences m_preferences;

        public override LanguagePreferences GetLanguagePreferences()
        {
            if (m_preferences == null)
            {
                m_preferences = new LanguagePreferences(this.Site,
                                                        typeof(HwdlLanguageService).GUID,
                                                        this.Name);
                m_preferences.Init();
            }

            return m_preferences;
        }


       private HwdlScanner m_scanner;

        public override IScanner GetScanner(IVsTextLines buffer)
        {
            if (m_scanner == null)
            {
                m_scanner = new HwdlScanner(buffer);
            }

            return m_scanner;
        }


        public override string Name
        {
            get { return "Human Workflow Definition Language"; }
        }

        public override Microsoft.VisualStudio.Package.Source CreateSource(IVsTextLines buffer)
        {
            return new HwdlSource(this, buffer, this.GetColorizer(buffer));
        }

        private Parser parser;
        private ParseTree parseTree;
        public IList<TextSpan[]> Braces {get; set;}

        public override AuthoringScope ParseSource(ParseRequest req)
        {
            HwdlAuthoringScope authoringScope = new HwdlAuthoringScope();
            switch (req.Reason)
            {
                case ParseReason.Check:

                    parseTree = (ParseTree)parser.Parse(req.Text, req.FileName);

                    if (parser.Context.CurrentParseTree.ParserMessages.Count == 0)
                    {
                        MatchBraces(parseTree);
                    }

                    if (parser.Context.CurrentParseTree.ParserMessages.Count > 0)
                    {
                            AddErrosToSink(req);
                    }
                    break;

                case ParseReason.HighlightBraces:
                case ParseReason.MemberSelectAndHighlightBraces:                                       
                    if (Braces != null)
                    {
                        AddBracesToSink(req);               
                    }
                    break;
                case ParseReason.QuickInfo:
                    ParseTreeNode currentNode = FindParseNode(req.Line, req.Col);
                    if (currentNode != null)
                    {
                        authoringScope.DataTipText = FindDefinitionForNode(currentNode);

                        TextSpan dataTipSpan = new TextSpan();
                        dataTipSpan.iStartLine = dataTipSpan.iEndLine = currentNode.Token.Location.Line;
                        dataTipSpan.iStartIndex = currentNode.Token.Location.Column;
                        dataTipSpan.iEndIndex = dataTipSpan.iStartIndex + currentNode.Token.Text.Length;
                        authoringScope.DataTipSpan = dataTipSpan;
                    }
                    else
                    {
                        authoringScope.DataTipText = "";
                        authoringScope.DataTipSpan = new TextSpan();
                    }

                    break;
            }

            return authoringScope;
        }

        string FindDefinitionForNode(ParseTreeNode treeNode)
        {
            List<string> declarationTermName = new List<string>();
            switch(treeNode.Term.Name)
            {
                case "it_done_by_role_ref":
                    declarationTermName.Add("it_role_def");
                    break;
                case "it_transition_target_step_ref":
                    declarationTermName.Add("it_manual_step_def");
                    declarationTermName.Add("it_base_step_def");
                    declarationTermName.Add("it_final_step_def");
                    break;
            }
            ParseTreeNode declarationNode = FindNodeByTextAndTermName(treeNode.Token.Text, declarationTermName);
 
            ParseTreeNode parent = GetParentNode(parseTree.Root, declarationNode);
            ParseTreeNode definition = null;
            if (parent != null)
            {
                
                definition = parent.ChildNodes.Find(tn => tn.Term.Name == "definitionComment");
            }

            if (definition != null)
            {
                return definition.Token.Text;
            }
            else
            {
                return "";
            }
        }

        private ParseTreeNode FindNodeByTextAndTermName(string text, List<string> orTermNameList)
        {
            return null;
        }
        private ParseTreeNode GetParentNode(ParseTreeNode treeNode, ParseTreeNode forChildNode)
        {
            ParseTreeNode result = null;

            if (treeNode.ChildNodes.Contains(forChildNode))
            {
                return treeNode;
            }
            else
            {
                foreach (ParseTreeNode childNode in treeNode.ChildNodes)
                {
                    result = GetParentNode(childNode, forChildNode);
                    if (result != null) break;
                }

                return result;
            }
        }

        ParseTreeNode FindParseNode(int line, int col)
        {
            if (parseTree != null)
            {
                return TraverseParseTreeForTokenSearch(parseTree.Root, line, col);
            }
            else
            {
                return null;
            }
        }

        private ParseTreeNode TraverseParseTreeForTokenSearch(ParseTreeNode treeNode, int line, int col)
        {
            ParseTreeNode result = null;

            if (treeNode.Token != null &&
                treeNode.Token.Location.Line == line &&
                treeNode.Token.Location.Column <= col &&
                treeNode.Token.Location.Column + treeNode.Token.Length >= col)
            {
                return treeNode;
            }
            else
            {
                foreach (ParseTreeNode childNode in treeNode.ChildNodes)
                {
                    result = TraverseParseTreeForTokenSearch(childNode, line, col);
                    if (result != null) break;
                }

                return result;
            }
        }

        private void AddBracesToSink(ParseRequest req)
        {
            foreach (TextSpan[] brace in Braces)
            {
                if (brace.Length == 2)
                    req.Sink.MatchPair(brace[0], brace[1], 1);
                else if (brace.Length >= 3)
                    req.Sink.MatchTriple(brace[0], brace[1], brace[2], 1);
            }
        }

        private void AddErrosToSink(ParseRequest req)
        {
            foreach (ParserMessage error in parser.Context.CurrentParseTree.ParserMessages)
            {
                TextSpan span = new TextSpan();
                span.iStartLine = span.iEndLine = error.Location.Line;
                span.iStartIndex = error.Location.Column;
                span.iEndIndex = error.Location.Position;
                req.Sink.AddError(req.FileName, error.Message, span, Severity.Error);
            }
        }

        #region Find and store matching braces

        private void MatchBraces(ParseTree parseTree)
        {
            Braces = new List<TextSpan[]>();
            TraverseParseTree(parseTree.Root);
        }

        private void TraverseParseTree(ParseTreeNode treeNode)
        {
            // In our grammar there is a _BLOCK postfixed non-terminal for curly braces
            if (treeNode.Term != null && treeNode.Term.Name.EndsWith("_BLOCK"))
            {
                SaveBraceMatch(treeNode.FirstChild, treeNode.LastChild);
            }

            foreach (ParseTreeNode childNode in treeNode.ChildNodes)
            {
                TraverseParseTree(childNode);
            }
        }

        private void SaveBraceMatch(ParseTreeNode openBraceNode, ParseTreeNode closeBraceNode)
        {
            TextSpan openBrace = new TextSpan();
            openBrace.iStartLine = openBrace.iEndLine = openBraceNode.Token.Location.Line ;
            openBrace.iStartIndex = openBraceNode.Token.Location.Column;
            openBrace.iEndIndex = openBrace.iStartIndex + 1;

            TextSpan closeBrace = new TextSpan();
            closeBrace.iStartLine = closeBrace.iEndLine = closeBraceNode.Token.Location.Line;
            closeBrace.iStartIndex = closeBraceNode.Token.Location.Column;
            closeBrace.iEndIndex = closeBrace.iStartIndex + 1;

            Braces.Add(new TextSpan[2] { openBrace, closeBrace });
        }

        #endregion
    }
}
